/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Hardware.h"
#include <win32/win32.h>
#include <GL/gl.h>
#include <WindowImpl.h>

#include "VertexBuffer.h"
#include "Texture.h"
#include "Tk.h"

namespace liba
{
namespace lib3d
{
namespace hard
{
namespace macx
{

class MatrixGL
{
	Matrix mat;
	static void low_save( const GLfloat * f )
	{
		glLoadMatrixf( f );
	}
public:
	MatrixGL( const Matrix & mat )
	:	mat( mat )
	{}
	void save()const
	{
		GLfloat data[16];
		for( int y = 0; y < 4; ++y )
			for( int x = 0; x < 4; ++x )
				data[ x*4 + y ] = mat.cr( x, y );
		low_save(data);
	}
};

Hardware::Hardware()
{
	Strings names;
	names.push_back("Fullscreen");
	names.push_back("ResolutionX");
	names.push_back("ResolutionY");
	names.push_back("Antialiasing");
	Strings values;
	values.push_back("Yes");
	values.push_back("640");
	values.push_back("480");
	values.push_back("None");

	set_attribute_names( names, values );

	Strings table_row( 4 );
	table_row[0] = "Yes";

	table_row[1] = "640";
	table_row[2] = "480";
	table_row[3] = "None";
	add_attribute_table_row( table_row );
}

Hardware::~Hardware()
{
	stop_mode();
}

bool Hardware::set_mode_internal(WindowImpl * window, const Strings & attribute_values)
{
	tkInitDisplayMode( TK_DOUBLE | TK_RGB | TK_DEPTH );
	tkInitPosition(0, 0, \
		GetSystemMetrics(SM_CXSCREEN), \
		GetSystemMetrics(SM_CYSCREEN),1 );

	tkCreateContext( window->get_wnd() );
	return true;
}

void Hardware::stop_mode()
{
	tkDestroyWindow();
}

hard::Texture * Hardware::create_texture(int mipmap_levels, const pictureformat::Format & desired_format, int width, int height)
{
	GLint max_texture_size = 0;
	glGetIntegerv( GL_MAX_TEXTURE_SIZE, &max_texture_size );
	return new Texture( this, mipmap_levels, pictureformat::R8G8B8A8, width, height, max_texture_size );
}

hard::VertexBuffer * Hardware::create_vertex_buffer( bool dynamic )
{
	return new VertexBuffer( this, dynamic );
}

bool Hardware::start_frame()
{
	return true;
}

void Hardware::flip()
{
	glFlush();
	tkSwapBuffers();
}
bool Hardware::start_render( const RenderList & render_list, RenderTarget * render_target )
{
	glDepthMask( true );
	if( render_list.clear_render_target )
	{
		glClearColor( 0, 0, 0, 0 );
		glClearDepth( 1 );
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	}
	else
	{
		glClearDepth( 1 );
		glClear( GL_DEPTH_BUFFER_BIT );
	}

	glShadeModel( GL_SMOOTH );
	glEnable( GL_LIGHTING );
	glEnable( GL_CULL_FACE );
	glEnable( GL_DEPTH_TEST );
	glEnable( GL_TEXTURE_2D );

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); 
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );

	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	glEnable( GL_BLEND );

	glEnableClientState( GL_VERTEX_ARRAY );
//	glEnableClientState( GL_NORMAL_ARRAY );
//	glEnableClientState( GL_COLOR_ARRAY );
	glEnableClientState( GL_TEXTURE_COORD_ARRAY );

	return true;
}

static void set_lights( RenderList * render_list )
{
	int max_lights = 8;
	glGetIntegerv( GL_MAX_LIGHTS, &max_lights );

	int i = 0;
	RenderLights::const_iterator lit = render_list->lights.begin();
	for( ; lit != render_list->lights.end(); ++lit )
	{
		int light_number = GL_LIGHT0 + i;

		GLfloat ambient[] = { 0, 0, 0, 1 };
		glLightfv( light_number, GL_AMBIENT, ambient );
		glLightfv( light_number, GL_SPECULAR, ambient );

		if( !lit->is_directional )
		{
			GLfloat position[] = { lit->world_position.x, lit->world_position.y, lit->world_position.z, 1 };
			glLightfv( light_number, GL_POSITION, position );

			GLfloat diffuse[] = { lit->diffuse.r, lit->diffuse.g, lit->diffuse.b, 1 };
			glLightfv( light_number, GL_DIFFUSE, diffuse );

			glLightf( light_number, GL_CONSTANT_ATTENUATION, lit->clq.x/lit->intensity );
			glLightf( light_number, GL_LINEAR_ATTENUATION, lit->clq.y/lit->intensity );
			glLightf( light_number, GL_QUADRATIC_ATTENUATION, lit->clq.z/lit->intensity );
		}
		else
		{
			GLfloat direction[] = { lit->world_direction.x, lit->world_direction.y, -lit->world_direction.z, 0 };
			glLightfv( light_number, GL_POSITION, direction );

			GLfloat diffuse[] = { lit->diffuse.r * lit->intensity, lit->diffuse.g * lit->intensity, lit->diffuse.b * lit->intensity, 1 };
			glLightfv( light_number, GL_DIFFUSE, diffuse );
		}

		glEnable( light_number );
		++i;
		if( i >= max_lights )
			break;
	}

	while( i < max_lights )
	{
		int light_number = GL_LIGHT0 + i;
		glDisable( light_number );
		++i;
	}
}

static void ren_model( const RenderModel & render_model )
{
	if( render_model.ib )
		render_model.vb->render( render_model.ib, render_model.primitive_start, render_model.primitive_count, render_model.vertex_start, render_model.vertex_count );
	else
		render_model.vb->render( render_model.primitive_start, render_model.primitive_count );
}

static void con_tex_ren_model_diffuse_reflecion_emissive( const RenderList & render_list, const RenderModel & render_model )
{
	TextureBase * ref = render_model.material.cubic_reflection_texture.texture;
	TextureBase * dif = render_model.material.diffuse_texture.texture;
	TextureBase * emi = render_model.material.emissive_texture.texture;

	//simplest style, no multitexturing
	if( !emi )
	{
		if( dif )
			dif->set_in_hardware( 0 );
		else
			glBindTexture( GL_TEXTURE_2D, 0 );

		ren_model( render_model );
	}
	else
	{
		emi->set_in_hardware( 0 );

		GLfloat emissive[4] = { render_model.material.emissive.r,
			render_model.material.emissive.g,
			render_model.material.emissive.b,
			0 };

		GLfloat diffuse[4];
		glGetMaterialfv( GL_FRONT, GL_DIFFUSE, (GLfloat*)&diffuse );
		diffuse[0] = diffuse[1] = diffuse[2] = 0;

		glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse );
		glMaterialfv( GL_FRONT, GL_EMISSION, emissive );

		glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
		ren_model( render_model );
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	}
}

static void prepare_model( const RenderModel & render_model, bool is_opaque )
{
	glPopMatrix();
	glPushMatrix();
	glMultMatrixf( (float *)&render_model.world );

	const double opacity = 1 - render_model.material.transparency;

	//see directx driver for details
	GLfloat diffuse[] = { render_model.material.diffuse.r * opacity,
		render_model.material.diffuse.g * opacity,
		render_model.material.diffuse.b * opacity,
		opacity };
	glMaterialfv( GL_FRONT, GL_DIFFUSE, diffuse );
	diffuse[3] = 0;
	glMaterialfv( GL_FRONT, GL_AMBIENT, diffuse );
}

static void set_z_less_write()
{
	glDepthFunc( GL_LESS );
	glDepthMask( true );
}

static void set_z_lessequal_nowrite()
{
	glDepthFunc( GL_LEQUAL );
	glDepthMask( false );
}

static void set_alpha_test_keep_only_1()
{
	glAlphaFunc( GL_EQUAL, 0xFF );
	glEnable( GL_ALPHA_TEST );
}

static void set_alpha_test_off()
{
	glDisable( GL_ALPHA_TEST );
}

void Hardware::render( RenderList * rl, RenderTarget * render_target )
{
	const RenderList & render_list = *rl;

	if( !start_render( render_list, render_target ) )
		return;

	rl->calculate( 0, 0 );

	glMatrixMode( GL_PROJECTION );
	glLoadMatrixf( (float *)&rl->camera_projection );
	//	MatrixGL( rl->camera_projection ).save();

	glMatrixMode( GL_MODELVIEW );
	glLoadMatrixf( (float *)&rl->view_mat );
	//	MatrixGL( rl->view_mat ).save();

	set_lights( rl );

	GLfloat global_ambient[] = { rl->ambient_color.r, rl->ambient_color.g, rl->ambient_color.b, 1 };
	glLightModelfv( GL_LIGHT_MODEL_AMBIENT, global_ambient );

	glPushMatrix();

	glCullFace( GL_FRONT );
	set_z_less_write();
	set_alpha_test_keep_only_1();
	{
		RenderModels::const_iterator cit = render_list.models.begin();
		for( ; cit != render_list.models.begin() + render_list.first_transparent_model; ++cit )
		{
			prepare_model( *cit, true );
			con_tex_ren_model_diffuse_reflecion_emissive( render_list, *cit );
		}
	}

	set_alpha_test_off();
	set_z_lessequal_nowrite();
	{
		RenderModels::const_iterator cit = render_list.models.begin() + render_list.first_transparent_model;
		RenderSprites::const_iterator sit = render_list.sprites.begin();
		for( ; cit != render_list.models.end(); ++cit )
		{
			//draw sprites in correct order
			bool entered_cycle = false;
			for( ; sit != render_list.sprites.end() && sit->unimportance < cit->unimportance; ++sit )
			{
				if( !entered_cycle )
				{
					entered_cycle = true;
					//TODO: draw sprites
					//					combiner_blend_for_sprite_pass();
					//					set_culling(D3DCULL_NONE);
					//					hr = device->SetVertexShader( device_objects->VSSprite );
				}
				//				con_sprite( render_list, *sit );
				//				tex_ren_sprite( render_list, *sit );
			}

			if( entered_cycle )
			{
				//				combiner_blend_for_diffuse_reflecion_emissive_pass();
				//				set_culling(D3DCULL_CCW);
			}

			prepare_model( *cit, false );
			con_tex_ren_model_diffuse_reflecion_emissive( render_list, *cit );
		}
	}

	glPopMatrix();
}

}
}
}
}